package com.liuqi.knet.remoting.transport;

import com.liuqi.knet.remoting.Channel;
import com.liuqi.knet.remoting.ChannelHandler;
import com.liuqi.knet.remoting.Client;
import com.liuqi.knet.remoting.ClientConfig;
import com.liuqi.knet.remoting.RemotingException;
import com.liuqi.knet.remoting.exchange.ResponseFuture;
import com.liuqi.knet.util.NetUtil;
import com.liuqi.loggers.api.Logger;
import com.liuqi.loggers.facade.LoggersFacade;
import java.net.InetSocketAddress;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author liuqi
 * @date 2025/3/6 13:44
 **/
public abstract class AbstractClient extends AbstractEndpoint implements Client {

    private static final Logger logger = LoggersFacade.getLogger(AbstractClient.class);

    private final Lock connectLock = new ReentrantLock();

    private final InetSocketAddress remoteAddress;
    private final int connectTimeout;
    private final int timeout;
    private final int retry;

    public AbstractClient(ChannelHandler handler, ClientConfig clientConfig) throws RemotingException {
        super(handler, clientConfig);

        this.remoteAddress = clientConfig.getRemoteAddress();
        this.connectTimeout = clientConfig.getConnectTimeout();
        this.timeout = clientConfig.getTimeout();
        this.retry = clientConfig.getConnectRetry() + 1;

        if (remoteAddress == null) {
            throw new NullPointerException("client remoteAddress == null");
        }

        try {
            doOpen();
        } catch (Throwable t) {
            close();
            throw new RemotingException("无法启动客户端. 远程服务端: " + remoteAddress + ", cause: " + t.getMessage(), t);
        }

        for (int i = 0; i < retry; i++) {
            try {
                connect();
                if (isConnected()) {    // 建立连接成功
                    break;
                }
            } catch (RemotingException t) {
                if (i == retry - 1) {
                    throw t;
                }
                logger.error(t.getMessage() + ", 发起第[" + (i + 1) + "]次重连,");
            }
        }

    }

    @Override
    public boolean isConnected() {
        return isActive();
    }

    @Override
    public void reconnect() throws RemotingException {
        disconnect();
        connect();
    }

    @Override
    public InetSocketAddress getLocalAddress() {
        Channel channel = getChannel();
        if (channel == null)
            return InetSocketAddress.createUnresolved(NetUtil.getLocalHost(), 0);
        return channel.getLocalAddress();
    }

    @Override
    public ResponseFuture request(Object request) throws RemotingException {
        return request(request, timeout);
    }

    @Override
    public ResponseFuture request(Object request, int timeout) throws RemotingException {
        Channel channel = getChannel();
        if (channel == null)
            return null;
        return channel.request(request, timeout);
    }

    @Override
    public void send(Object message, boolean sent) throws RemotingException {
        if (!isConnected()) {
            throw new RemotingException("客户端未建立连接. remoteAddress: " + getRemoteAddress());
        }
        Channel channel = getChannel();
        if (channel == null || !channel.isActive()) {
            throw new RemotingException("消息无法发送, 通道不活跃. remoteAddress: " + getRemoteAddress());
        }
        channel.send(message, sent);
    }

    @Override
    public InetSocketAddress getRemoteAddress() {
        Channel channel = getChannel();
        if (channel == null)
            return remoteAddress;
        return channel.getRemoteAddress();
    }

    @Override
    public boolean isActive() {
        Channel channel = getChannel();
        if (channel == null)
            return false;
        return channel.isActive();
    }

    @Override
    public boolean hasAttribute(String key) {
        Channel channel = getChannel();
        if (channel == null)
            return false;
        return channel.hasAttribute(key);
    }

    @Override
    public Object getAttribute(String key) {
        Channel channel = getChannel();
        if (channel == null)
            return null;
        return channel.getAttribute(key);
    }

    @Override
    public void setAttribute(String key, Object value) {
        Channel channel = getChannel();
        if (channel == null)
            return;
        channel.setAttribute(key, value);
    }

    @Override
    public void removeAttribute(String key) {
        Channel channel = getChannel();
        if (channel == null)
            return;
        channel.removeAttribute(key);
    }

    @Override
    public void close() {
        super.close();
        try {
            disconnect();
        } catch (Throwable e) {
            logger.warn(e.getMessage(), e);
        }
        try {
            doClose();
        } catch (Throwable e) {
            logger.warn(e.getMessage(), e);
        }
    }

    private void connect() throws RemotingException {
        connectLock.lock();
        try {
            if (isConnected()) {
                return;
            }
            // connect
            doConnect();
            if (!isConnected()) {
                throw new RemotingException("连接远程服务端失败, 远程服务端: " + remoteAddress + ", 连接等待超时: " + getConnectTimeout());
            } else {
                logger.info("连接远程服务端成功, 远程服务端: " + remoteAddress);
            }
        } catch (RemotingException e) {
            disconnect();
            throw e;
        } catch (Throwable t) {
            disconnect();
            throw new RemotingException("连接远程服务端失败, 远程服务端: " + remoteAddress +
                    ", 连接等待超时: " + getConnectTimeout() + ", cause: " + t.getMessage(), t);
        } finally {
            connectLock.unlock();
        }
    }

    protected void disconnect() {
        connectLock.lock();
        try {
            try {
                Channel channel = getChannel();
                if (channel != null) {
                    channel.close();
                }
            } catch (Throwable t) {
                logger.warn(t.getMessage(), t);
            }
            try {
                doDisConnect();
            } catch (Throwable e) {
                logger.warn(e.getMessage(), e);
            }
        } finally {
            connectLock.unlock();
        }
    }

    protected int getConnectTimeout() {
        return connectTimeout;
    }

    protected abstract void doOpen() throws Throwable;

    protected abstract void doConnect() throws Throwable;

    protected abstract void doClose() throws Throwable;

    protected abstract void doDisConnect() throws Throwable;

    protected abstract Channel getChannel();

}
